﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;

using LostLib.Collections.Generic;

namespace LostLib.Programs
{
	/// <summary>
	/// Represents user filter for package versions.
	/// </summary>
	[Serializable]
	public class PackageVersionFilter : IFormattable
	{
		/// <summary>
		/// Builds dependency tree for given packages.
		/// </summary>
		/// <param name="db">Database to get information from.</param>
		/// <param name="packageFilter">Filter to specify packages.</param>
		/// <param name="tree">Target tree.</param>
		/// <returns>Last root of tree hierarchy.</returns>
		public PackageVersion BuildDependancyTree(
			ApplicationsDatabaseDataContext db,
			PackageVersionFilter packageFilter,
			VersionDependencyTree tree)
		{
			#region поиск установленной версии
			var version = (packageFilter.Filter(from ver in db.PackageVersions
												where ver.Installed
												select ver)).FirstOrDefault();

			if (version != null) return version;
			#endregion поиск установленной версии

			//нет установленной версии:

			#region поиск нужного пакета
			var pack = (from package in db.Packages
						where package.PackageName == packageFilter.PackageName
						select package).FirstOrDefault();

			if (pack == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable package for filter {0}", packageFilter));
			#endregion поиск нужного пакета
			//pack = нужный пакет

			#region поиск подходящей версии
			version = pack.LatestSuitable;
			if (version == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable version for filter {0}", packageFilter));

			if (tree.ContainsKey(version)) return version;
			#endregion поиск подходящей версии
			//version = подходящая не установленная и ранее не просмотренная версия

			var list = new List<PackageVersion>();
			tree.Add(version, list);

			var dependancies = from dependancy in version.VersionDependancies
							   where dependancy.DependancyType == "newer"
							   select dependancy;

			if (version.VirtualPackage)
			{
				var suitable = null as VersionDependancy;
				foreach (var dependancy in dependancies)
				{
					var package = dependancy.Package;
					if (package == null) throw new KeyNotFoundException(pack.PackageName);

					var installed = package.InstalledVersion;
					if ((installed != null) && (installed.Version >= dependancy.Version))
						return installed;

					if (suitable == null)
						suitable = package.LatestSuitable == null? null: dependancy;
				}

				if (suitable == null) throw new KeyNotFoundException(string.Format(CultureInfo.CurrentCulture, "there is no suitable version for virtual package {0}", version.PackageName));


				var versionDep =
					BuildDependancyTree(db,
						new PackageVersionFilter() {
							PackageName = suitable.PackageName,
							Version = suitable.Version,
						}, tree);
				list.Add(versionDep);

				return version;
			} else
				foreach (var dependancy in dependancies)
				{
					var versionDep =
						BuildDependancyTree(db,
							new PackageVersionFilter() {
								PackageName = dependancy.PackageName,
								Version = dependancy.Version,
							}, tree);

					if (!versionDep.Installed)
						list.Add(versionDep);
				}
			return version;
		}

		/// <summary>
		/// Converts the specified string representation of version filter into PackageVersionFilter object.
		/// </summary>
		/// <param name="input">Input string.</param>
		/// <param name="formatProvider">Format provider.</param>
		/// <returns>PackageVersionFilter object.</returns>
		public static PackageVersionFilter Parse(string input, IFormatProvider formatProvider)
		{
			string[] namever = input.Split(new string[] { "newer" }, StringSplitOptions.None);
			string name = namever[0];
			DateTime? ver = namever.Length == 1 ? (DateTime?)null : DateTime.Parse(namever[1], formatProvider);

			var result = new PackageVersionFilter() {
				PackageName = name,
				Version = ver,
			};
			return result;
		}

		/// <summary>
		/// Required package name.
		/// </summary>
		public string PackageName { get; set; }
		/// <summary>
		/// Minimum package version.
		/// </summary>
		public DateTime? Version { get; set; }

		/// <summary>
		/// Filters list of versions.
		/// </summary>
		/// <param name="versions">Version list.</param>
		/// <returns>Filtered IQueryable.</returns>
		public IQueryable<PackageVersion> Filter(IQueryable<PackageVersion> versions)
		{
			return Version.HasValue
				? from version in versions
				  where
					  (version.PackageName == PackageName)
					  && (version.Version >= Version.Value)
				  select version
				: from version in versions
				  where
					  (version.PackageName == PackageName)
				  select version;


		}

		#region IFormattable Members
		/// <summary>
		/// Converts the specified PackageVersionFilter to its string representation.
		/// </summary>
		/// <returns>String representation of PackageVersionFilter.</returns>
		public override string ToString()
		{
			if (Version.HasValue)
				return string.Format(CultureInfo.CurrentCulture, "{0}>={1}", PackageName,
					Version.Value.ToString("d", CultureInfo.CurrentCulture));
			else
				return string.Format(CultureInfo.CurrentCulture, "{0}",
					PackageName);
		}

		/// <summary>
		/// Converts the specified PackageVersionFilter to its string representation.
		/// </summary>
		/// <param name="format">Format.</param>
		/// <param name="formatProvider">Format provider.</param>
		/// <returns>String representation of PackageVersionFilter.</returns>
		public string ToString(string format, IFormatProvider formatProvider)
		{
			if (Version.HasValue)
				return string.Format(formatProvider, "{0}>={1}", PackageName,
					Version.Value.ToString("d", formatProvider));
			else
				return string.Format(formatProvider, "{0}",
					PackageName);
		}

		#endregion
	}
}
